/**
 * @file AsideNav
 * @description 左侧导航。
 * @author fex
 */

import * as React from 'react';
import * as cx from 'classnames';
import {mapTree} from '../utils/helper';
import {classPrefix, classnames} from '../themes/default';
import {ClassNamesFn, themeable} from '../theme';

export type LinkItem = LinkItemProps;
interface LinkItemProps {
    id?: number;
    label: string;
    hidden?: boolean;
    open: boolean;
    active: boolean;
    className?: string;
    children?: Array<LinkItem>;
}

interface Navigation {
    label: string;
    children: Array<LinkItem>;
    prefix?: JSX.Element;
    affix?: JSX.Element;
    className?: string;
    [propName: string]: any;
}

interface AsideNavProps {
    id?: string;
    className?: string;
    classPrefix: string;
    classnames: ClassNamesFn;
    renderLink: Function;
    isActive: Function;
    isOpen: (link: LinkItemProps) => boolean;
    navigations: Array<Navigation>;
    renderSubLinks: (link: LinkItemProps, renderLink: Function, depth: number, props: AsideNavProps) => React.ReactNode;
}

interface AsideNavState {
    navigations: Array<Navigation>;
}

export class AsideNav extends React.Component<AsideNavProps, AsideNavState> {
    static defaultProps = {
        renderLink: (item: LinkItemProps) => <a>{item.label}</a>,
        renderSubLinks: (link: LinkItemProps, renderLink: Function, depth: number, {classnames: cx}: AsideNavProps) =>
            link.children && link.children.length ? (
                <ul className={cx('AsideNav-subList')}>
                    {link.label ? (
                        <li key="subHeader" className={cx('AsideNav-subHeader')}>
                            <a>{link.label}</a>
                        </li>
                    ) : null}
                    {link.children.map((link, key) => renderLink(link, key, {}, depth + 1))}
                </ul>
            ) : link.label && depth === 1 ? (
                <div className={cx('AsideNav-tooltip')}>{link.label}</div>
            ) : null,
        isActive: (link: LinkItem) => link.open,
        isOpen: (item: LinkItemProps) => (item.children ? item.children.some(item => item.open) : false),
    };

    constructor(props: AsideNavProps) {
        super(props);

        const isOpen = props.isOpen;
        let id = 1;
        this.state = {
            navigations: mapTree(
                props.navigations,
                (item: Navigation) => {
                    const isActive =
                        typeof item.active === 'undefined' ? (props.isActive as Function)(item) : item.active;

                    return {
                        ...item,
                        id: id++,
                        active: isActive,
                        open: isActive || isOpen(item as LinkItemProps),
                    };
                },
                1,
                true
            ),
        };

        this.renderLink = this.renderLink.bind(this);
        this.toggleExpand = this.toggleExpand.bind(this);
    }

    componentWillReceiveProps(nextProps: AsideNavProps) {
        const props = this.props;
        const isOpen = props.isOpen;

        if (props.navigations !== nextProps.navigations || props.isActive !== nextProps.isActive) {
            let id = 1;
            this.setState({
                navigations: mapTree(
                    nextProps.navigations,
                    (item: Navigation) => {
                        const isActive =
                            typeof item.active === 'undefined' ? (nextProps.isActive as Function)(item) : item.active;

                        return {
                            ...item,
                            id: id++,
                            active: isActive,
                            open: isActive || isOpen(item as LinkItemProps),
                        };
                    },
                    1,
                    true
                ),
            });
        }
    }

    toggleExpand(link: LinkItemProps) {
        this.setState({
            navigations: mapTree(
                this.state.navigations,
                (item: Navigation) => ({
                    ...item,
                    open: link.id === item.id ? !item.open : item.open,
                }),
                1,
                true
            ),
        });
    }

    renderLink(link: LinkItemProps, key: any, props: Partial<AsideNavProps> = {}, depth = 1): React.ReactNode {
        const {renderLink, isActive, renderSubLinks, classnames: cx, ...others} = this.props;

        const dom = (renderLink as Function)({
            link,
            active: link.active,
            open: link.open,
            toggleExpand: this.toggleExpand,
            depth,
            classnames: cx,
            ...others,
        });

        if (!dom) {
            return;
        }

        return (
            <li
                {...props}
                key={key}
                className={cx(`AsideNav-item`, link.className, {
                    [`is-active`]: link.active || link.open,
                })}
            >
                {dom}
                {renderSubLinks(link, this.renderLink, depth, this.props)}
            </li>
        );
    }

    render() {
        const navigations = this.state.navigations;
        let links: Array<React.ReactNode> = [];
        const {className, classnames: cx} = this.props;

        navigations.forEach((navigation, index) => {
            if (navigation.prefix) {
                const prefix: JSX.Element =
                    typeof navigation.prefix === 'function'
                        ? (navigation.prefix as any)(this.props)
                        : navigation.prefix;
                links.push(
                    React.cloneElement(prefix, {
                        ...prefix.props,
                        key: `${index}-prefix`,
                    })
                );
            }

            navigation.label &&
                links.push(
                    <li key={`${index}-label`} className={cx(`AsideNav-label`, navigation.className)}>
                        <span>{navigation.label}</span>
                    </li>
                );

            navigation.children.forEach((item, key) => {
                const link = this.renderLink(item, `${index}-${key}`);
                link && links.push(link);
            });

            if (navigation.affix) {
                const affix: JSX.Element =
                    typeof navigation.affix === 'function' ? (navigation.affix as any)(this.props) : navigation.affix;
                links.push(
                    React.cloneElement(affix, {
                        ...affix.props,
                        key: `${index}-affix`,
                    })
                );
            }
        });

        return (
            <nav className={cx(`AsideNav`, className)}>
                <ul className={cx(`AsideNav-list`)}>{links}</ul>
            </nav>
        );
    }
}

export default themeable(AsideNav);
